'''
Created on Feb 3, 2013

@author: trananh
'''
import numpy

class ConfMatrix(object):
    """
    Models a confusion matrix.
    """

    def __init__(self, classlabels, matrix=None):
        """
        Creates a confusion matrix.
        """
        self.classlabels = classlabels
        if matrix is None:
            self.matrix = numpy.zeros((len(classlabels),len(classlabels)),dtype=numpy.int)
        else:
            self.matrix = matrix
        
    def add(self, item):
        """
        Adds a new item to the confusion matrix.
        """
        ilabel = self.classlabels.index(item.label)
        ipred = self.classlabels.index(item.prediction)
        self.matrix[ilabel][ipred] += 1
    
    def addAll(self, items):
        """
        Adds all new items to the confusion matrix.
        """
        for i in items:
            self.add(i)
            
    def accuracy(self):
        """
        Returns the accuracy statistic.
        """
        return float(numpy.trace(self.matrix)) / numpy.sum(self.matrix)
    
    def recall(self, label):
        """
        Returns the recall for the specified class.
        """
        # recall = tp / (tp + fn) = tp / row
        i = self.classlabels.index(label)
        return float(self.matrix[i,i]) / numpy.sum(self.matrix[i,:]) 
    
    def precision(self, label):
        """
        Returns the precision for the specified class.
        """
        # precision = tp / (tp + fp) = tp / column
        i = self.classlabels.index(label)
        return float(self.matrix[i,i]) / numpy.sum(self.matrix[:,i]) 
    
    def printSummary(self, probMode=True, latexMode=False):
        self.printMatrix(probMode=probMode,latexMode=latexMode)
        print
        print 'Accuracy: ' + str(self.accuracy())
        print 'Hits: ' + str(numpy.trace(self.matrix))
        print 'Misses: ' + str(numpy.sum(self.matrix) - numpy.trace(self.matrix))
    
    def printMatrix(self, probMode=True, latexMode=False):
        pad = max(max([len(s) for s in self.classlabels]), 5)
        if latexMode:
            delimiter = ' & '
            newline = ' \\\\'
        else:
            delimiter = ' '
            newline = ''
        
        print ''.ljust(pad) + delimiter + delimiter.join([s.ljust(pad) for s in self.classlabels]) + newline
        for i in range(numpy.shape(self.matrix)[0]):
            if probMode:
                row = self.matrix[i,:] / float(numpy.sum(self.matrix[i,:]))
                print self.classlabels[i].ljust(pad) + delimiter + \
                    delimiter.join([('%0.2f'%x).ljust(pad) for x in row]) + newline
            else:
                print self.classlabels[i].ljust(pad) + delimiter + \
                    delimiter.join([str(x).ljust(pad) for x in self.matrix[i,:]]) + newline
    
    @classmethod
    def compute(cls, items):
        """
        Computes the confusion matrix for the list of predicted items.
        
        PARAMETER(S):
            items - list of items with actual and prediction labels.
        """
        classes = set([i.label for i in items])
        classes = sorted(list(classes))
        
        cmatrix = ConfMatrix(classes)
        cmatrix.addAll(items)
        
        return cmatrix
    